package eval

import (
	"showen/num"
	"showen/obj"
	"testing"
)

func TestIsVectorProc(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	tests := []struct {
		name     string
		args     []obj.Object
		expected bool
		hasError bool
	}{
		{
			name:     "vector? with vector",
			args:     []obj.Object{&obj.VectorObject{Elements: []obj.Object{}}},
			expected: true,
			hasError: false,
		},
		{
			name:     "vector? with non-vector",
			args:     []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 5}}},
			expected: false,
			hasError: false,
		},
		{
			name:     "vector? with no arguments",
			args:     []obj.Object{},
			expected: false,
			hasError: true,
		},
		{
			name:     "vector? with two arguments",
			args:     []obj.Object{&obj.VectorObject{}, &obj.VectorObject{}},
			expected: false,
			hasError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := IsVectorProc(tt.args, env)

			if tt.hasError {
				if result.Type() != obj.ERROR_OBJ {
					t.Errorf("Expected error, got %v", result)
				}
				return
			}

			if result.Type() != obj.BOOLEAN_OBJ {
				t.Errorf("Expected boolean result, got %v", result.Type())
				return
			}

			boolResult := result.(*obj.BooleanObject)
			if boolResult.Value != tt.expected {
				t.Errorf("Expected %v, got %v", tt.expected, boolResult.Value)
			}
		})
	}
}

func TestMakeVector(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	tests := []struct {
		name     string
		args     []obj.Object
		hasError bool
		checkFn  func(*testing.T, obj.Object)
	}{
		{
			name:     "make-vector with size only",
			args:     []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 3}}},
			hasError: false,
			checkFn: func(t *testing.T, result obj.Object) {
				if result.Type() != obj.VECTOR_OBJ {
					t.Errorf("Expected vector, got %v", result.Type())
					return
				}
				vector := result.(*obj.VectorObject)
				if len(vector.Elements) != 3 {
					t.Errorf("Expected vector of length 3, got %d", len(vector.Elements))
				}
				for _, elem := range vector.Elements {
					if elem != obj.NULL_OBJECT {
						t.Errorf("Expected null elements, got %v", elem)
					}
				}
			},
		},
		{
			name: "make-vector with size and fill",
			args: []obj.Object{
				&obj.NumberObject{Value: num.Integer{Value: 2}},
				&obj.NumberObject{Value: num.Integer{Value: 5}},
			},
			hasError: false,
			checkFn: func(t *testing.T, result obj.Object) {
				if result.Type() != obj.VECTOR_OBJ {
					t.Errorf("Expected vector, got %v", result.Type())
					return
				}
				vector := result.(*obj.VectorObject)
				if len(vector.Elements) != 2 {
					t.Errorf("Expected vector of length 2, got %d", len(vector.Elements))
				}
				for _, elem := range vector.Elements {
					if elem.Type() != obj.NUMBER_OBJ {
						t.Errorf("Expected number elements, got %v", elem.Type())
						return
					}
					numObj := elem.(*obj.NumberObject)
					if numObj.Value.(num.Integer).Value != 5 {
						t.Errorf("Expected elements to be 5, got %v", numObj.Value)
					}
				}
			},
		},
		{
			name:     "make-vector with negative size",
			args:     []obj.Object{&obj.NumberObject{Value: num.Integer{Value: -1}}},
			hasError: true,
		},
		{
			name:     "make-vector with non-integer size",
			args:     []obj.Object{&obj.NumberObject{Value: num.Real{Value: 3.5}}},
			hasError: true,
		},
		{
			name:     "make-vector with no arguments",
			args:     []obj.Object{},
			hasError: true,
		},
		{
			name: "make-vector with three arguments",
			args: []obj.Object{
				&obj.NumberObject{Value: num.Integer{Value: 2}},
				&obj.NumberObject{Value: num.Integer{Value: 5}},
				&obj.NumberObject{Value: num.Integer{Value: 7}},
			},
			hasError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := MakeVector(tt.args, env)

			if tt.hasError {
				if result.Type() != obj.ERROR_OBJ {
					t.Errorf("Expected error, got %v", result)
				}
				return
			}

			if tt.checkFn != nil {
				tt.checkFn(t, result)
			}
		})
	}
}

func TestVectorFunction(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	tests := []struct {
		name    string
		args    []obj.Object
		checkFn func(*testing.T, obj.Object)
	}{
		{
			name: "vector with no arguments",
			args: []obj.Object{},
			checkFn: func(t *testing.T, result obj.Object) {
				if result.Type() != obj.VECTOR_OBJ {
					t.Errorf("Expected vector, got %v", result.Type())
					return
				}
				vector := result.(*obj.VectorObject)
				if len(vector.Elements) != 0 {
					t.Errorf("Expected empty vector, got length %d", len(vector.Elements))
				}
			},
		},
		{
			name: "vector with elements",
			args: []obj.Object{
				&obj.NumberObject{Value: num.Integer{Value: 1}},
				&obj.NumberObject{Value: num.Integer{Value: 2}},
				&obj.NumberObject{Value: num.Integer{Value: 3}},
			},
			checkFn: func(t *testing.T, result obj.Object) {
				if result.Type() != obj.VECTOR_OBJ {
					t.Errorf("Expected vector, got %v", result.Type())
					return
				}
				vector := result.(*obj.VectorObject)
				if len(vector.Elements) != 3 {
					t.Errorf("Expected vector of length 3, got %d", len(vector.Elements))
				}
				for i, elem := range vector.Elements {
					if elem.Type() != obj.NUMBER_OBJ {
						t.Errorf("Expected number element, got %v", elem.Type())
						return
					}
					numObj := elem.(*obj.NumberObject)
					expected := int64(i + 1)
					if numObj.Value.(num.Integer).Value != expected {
						t.Errorf("Expected element %d to be %d, got %v", i, expected, numObj.Value)
					}
				}
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := Vector(tt.args, env)

			if result.Type() == obj.ERROR_OBJ {
				t.Errorf("Unexpected error: %v", result.(*Error).Message)
				return
			}

			if tt.checkFn != nil {
				tt.checkFn(t, result)
			}
		})
	}
}

func TestVectorLength(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	tests := []struct {
		name     string
		args     []obj.Object
		expected int64
		hasError bool
	}{
		{
			name:     "vector-length of empty vector",
			args:     []obj.Object{&obj.VectorObject{Elements: []obj.Object{}}},
			expected: 0,
			hasError: false,
		},
		{
			name:     "vector-length of vector with elements",
			args:     []obj.Object{&obj.VectorObject{Elements: make([]obj.Object, 5)}},
			expected: 5,
			hasError: false,
		},
		{
			name:     "vector-length with non-vector",
			args:     []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 5}}},
			hasError: true,
		},
		{
			name:     "vector-length with no arguments",
			args:     []obj.Object{},
			hasError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := VectorLength(tt.args, env)

			if tt.hasError {
				if result.Type() != obj.ERROR_OBJ {
					t.Errorf("Expected error, got %v", result)
				}
				return
			}

			if result.Type() != obj.NUMBER_OBJ {
				t.Errorf("Expected number result, got %v", result.Type())
				return
			}

			numResult := result.(*obj.NumberObject)
			if numResult.Value.(num.Integer).Value != tt.expected {
				t.Errorf("Expected %d, got %v", tt.expected, numResult.Value)
			}
		})
	}
}

func TestVectorRef(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	vector := &obj.VectorObject{
		Elements: []obj.Object{
			&obj.NumberObject{Value: num.Integer{Value: 10}},
			&obj.NumberObject{Value: num.Integer{Value: 20}},
			&obj.NumberObject{Value: num.Integer{Value: 30}},
		},
	}

	tests := []struct {
		name     string
		args     []obj.Object
		expected obj.Object
		hasError bool
	}{
		{
			name: "vector-ref valid index",
			args: []obj.Object{
				vector,
				&obj.NumberObject{Value: num.Integer{Value: 1}},
			},
			expected: &obj.NumberObject{Value: num.Integer{Value: 20}},
			hasError: false,
		},
		{
			name: "vector-ref out of bounds",
			args: []obj.Object{
				vector,
				&obj.NumberObject{Value: num.Integer{Value: 5}},
			},
			hasError: true,
		},
		{
			name: "vector-ref negative index",
			args: []obj.Object{
				vector,
				&obj.NumberObject{Value: num.Integer{Value: -1}},
			},
			hasError: true,
		},
		{
			name: "vector-ref non-vector",
			args: []obj.Object{
				&obj.NumberObject{Value: num.Integer{Value: 5}},
				&obj.NumberObject{Value: num.Integer{Value: 0}},
			},
			hasError: true,
		},
		{
			name: "vector-ref non-integer index",
			args: []obj.Object{
				vector,
				&obj.NumberObject{Value: num.Real{Value: 1.5}},
			},
			hasError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := VectorRef(tt.args, env)

			if tt.hasError {
				if result.Type() != obj.ERROR_OBJ {
					t.Errorf("Expected error, got %v", result)
				}
				return
			}

			// Compare the values
			if result.Type() != tt.expected.Type() {
				t.Errorf("Expected type %v, got %v", tt.expected.Type(), result.Type())
				return
			}

			expectedNum := tt.expected.(*obj.NumberObject)
			actualNum := result.(*obj.NumberObject)
			if expectedNum.Value != actualNum.Value {
				t.Errorf("Expected %v, got %v", expectedNum.Value, actualNum.Value)
			}
		})
	}
}

func TestVectorSet(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	// Create a test vector
	vector := &obj.VectorObject{
		Elements: []obj.Object{
			&obj.NumberObject{Value: num.Integer{Value: 10}},
			&obj.NumberObject{Value: num.Integer{Value: 20}},
			&obj.NumberObject{Value: num.Integer{Value: 30}},
		},
	}

	tests := []struct {
		name     string
		args     []obj.Object
		checkFn  func(*testing.T, obj.Object, *obj.VectorObject)
		hasError bool
	}{
		{
			name: "vector-set! valid index",
			args: []obj.Object{
				vector,
				&obj.NumberObject{Value: num.Integer{Value: 1}},
				&obj.NumberObject{Value: num.Integer{Value: 99}},
			},
			checkFn: func(t *testing.T, result obj.Object, v *obj.VectorObject) {
				// Check that the result is NULL_OBJECT
				if result != obj.NULL_OBJECT {
					t.Errorf("Expected NULL_OBJECT, got %v", result)
				}

				// Check that the vector was modified
				if v.Elements[1].Type() != obj.NUMBER_OBJ {
					t.Errorf("Expected number at index 1, got %v", v.Elements[1].Type())
					return
				}

				numObj := v.Elements[1].(*obj.NumberObject)
				if numObj.Value.(num.Integer).Value != 99 {
					t.Errorf("Expected 99 at index 1, got %v", numObj.Value)
				}
			},
			hasError: false,
		},
		{
			name: "vector-set! out of bounds",
			args: []obj.Object{
				vector,
				&obj.NumberObject{Value: num.Integer{Value: 5}},
				&obj.NumberObject{Value: num.Integer{Value: 99}},
			},
			hasError: true,
		},
		{
			name: "vector-set! non-vector",
			args: []obj.Object{
				&obj.NumberObject{Value: num.Integer{Value: 5}},
				&obj.NumberObject{Value: num.Integer{Value: 0}},
				&obj.NumberObject{Value: num.Integer{Value: 99}},
			},
			hasError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create a copy of the vector for each test to avoid side effects
			testVector := &obj.VectorObject{
				Elements: make([]obj.Object, len(vector.Elements)),
			}
			copy(testVector.Elements, vector.Elements)

			args := make([]obj.Object, len(tt.args))
			copy(args, tt.args)
			// Only replace the first argument with testVector for valid test cases
			if len(args) > 0 && !tt.hasError {
				args[0] = testVector
			}

			result := VectorSet(args, env)

			if tt.hasError {
				if result.Type() != obj.ERROR_OBJ {
					t.Errorf("Expected error, got %v", result)
				}
				return
			}

			if tt.checkFn != nil {
				tt.checkFn(t, result, testVector)
			}
		})
	}
}

func TestVectorToList(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	tests := []struct {
		name     string
		args     []obj.Object
		checkFn  func(*testing.T, obj.Object)
		hasError bool
	}{
		{
			name: "vector->list empty vector",
			args: []obj.Object{&obj.VectorObject{Elements: []obj.Object{}}},
			checkFn: func(t *testing.T, result obj.Object) {
				// Should return empty list
				if result != obj.EMPTY_LIST {
					t.Errorf("Expected empty list, got %v", result)
				}
			},
			hasError: false,
		},
		{
			name: "vector->list with elements",
			args: []obj.Object{&obj.VectorObject{
				Elements: []obj.Object{
					&obj.NumberObject{Value: num.Integer{Value: 1}},
					&obj.NumberObject{Value: num.Integer{Value: 2}},
					&obj.NumberObject{Value: num.Integer{Value: 3}},
				},
			}},
			checkFn: func(t *testing.T, result obj.Object) {
				if result.Type() != obj.PAIR_OBJ {
					t.Errorf("Expected pair, got %v", result.Type())
					return
				}

				// Check that we have a list with 3 elements
				pair := result.(*obj.PairObject)
				if pair.Car.(*obj.NumberObject).Value.(num.Integer).Value != 1 {
					t.Errorf("Expected first element to be 1, got %v", pair.Car)
				}

				second := pair.Cdr.(*obj.PairObject)
				if second.Car.(*obj.NumberObject).Value.(num.Integer).Value != 2 {
					t.Errorf("Expected second element to be 2, got %v", second.Car)
				}

				third := second.Cdr.(*obj.PairObject)
				if third.Car.(*obj.NumberObject).Value.(num.Integer).Value != 3 {
					t.Errorf("Expected third element to be 3, got %v", third.Car)
				}

				if third.Cdr != obj.EMPTY_LIST {
					t.Errorf("Expected end of list, got %v", third.Cdr)
				}
			},
			hasError: false,
		},
		{
			name:     "vector->list with non-vector",
			args:     []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 5}}},
			hasError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := VectorToList(tt.args, env)

			if tt.hasError {
				if result.Type() != obj.ERROR_OBJ {
					t.Errorf("Expected error, got %v", result)
				}
				return
			}

			if tt.checkFn != nil {
				tt.checkFn(t, result)
			}
		})
	}
}

func TestListToVector(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	// Create a test list: (1 2 3)
	list := &obj.PairObject{
		Car: &obj.NumberObject{Value: num.Integer{Value: 1}},
		Cdr: &obj.PairObject{
			Car: &obj.NumberObject{Value: num.Integer{Value: 2}},
			Cdr: &obj.PairObject{
				Car: &obj.NumberObject{Value: num.Integer{Value: 3}},
				Cdr: obj.EMPTY_LIST,
			},
		},
	}

	tests := []struct {
		name     string
		args     []obj.Object
		checkFn  func(*testing.T, obj.Object)
		hasError bool
	}{
		{
			name: "list->vector empty list",
			args: []obj.Object{obj.EMPTY_LIST},
			checkFn: func(t *testing.T, result obj.Object) {
				if result.Type() != obj.VECTOR_OBJ {
					t.Errorf("Expected vector, got %v", result.Type())
					return
				}

				vector := result.(*obj.VectorObject)
				if len(vector.Elements) != 0 {
					t.Errorf("Expected empty vector, got length %d", len(vector.Elements))
				}
			},
			hasError: false,
		},
		{
			name: "list->vector with elements",
			args: []obj.Object{list},
			checkFn: func(t *testing.T, result obj.Object) {
				if result.Type() != obj.VECTOR_OBJ {
					t.Errorf("Expected vector, got %v", result.Type())
					return
				}

				vector := result.(*obj.VectorObject)
				if len(vector.Elements) != 3 {
					t.Errorf("Expected vector of length 3, got %d", len(vector.Elements))
					return
				}

				for i, elem := range vector.Elements {
					if elem.Type() != obj.NUMBER_OBJ {
						t.Errorf("Expected number element, got %v", elem.Type())
						return
					}
					numObj := elem.(*obj.NumberObject)
					expected := int64(i + 1)
					if numObj.Value.(num.Integer).Value != expected {
						t.Errorf("Expected element %d to be %d, got %v", i, expected, numObj.Value)
					}
				}
			},
			hasError: false,
		},
		{
			name:     "list->vector with non-list",
			args:     []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 5}}},
			hasError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := ListToVector(tt.args, env)

			if tt.hasError {
				if result.Type() != obj.ERROR_OBJ {
					t.Errorf("Expected error, got %v", result)
				}
				return
			}

			if tt.checkFn != nil {
				tt.checkFn(t, result)
			}
		})
	}
}

func TestVectorFill(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	// Create a test vector
	vector := &obj.VectorObject{
		Elements: []obj.Object{
			&obj.NumberObject{Value: num.Integer{Value: 10}},
			&obj.NumberObject{Value: num.Integer{Value: 20}},
			&obj.NumberObject{Value: num.Integer{Value: 30}},
		},
	}

	tests := []struct {
		name     string
		args     []obj.Object
		checkFn  func(*testing.T, obj.Object, *obj.VectorObject)
		hasError bool
	}{
		{
			name: "vector-fill! with value",
			args: []obj.Object{
				vector,
				&obj.NumberObject{Value: num.Integer{Value: 99}},
			},
			checkFn: func(t *testing.T, result obj.Object, v *obj.VectorObject) {
				// Check that the result is NULL_OBJECT
				if result != obj.NULL_OBJECT {
					t.Errorf("Expected NULL_OBJECT, got %v", result)
				}

				// Check that all elements were filled
				for _, elem := range v.Elements {
					if elem.Type() != obj.NUMBER_OBJ {
						t.Errorf("Expected number element, got %v", elem.Type())
						return
					}
					numObj := elem.(*obj.NumberObject)
					if numObj.Value.(num.Integer).Value != 99 {
						t.Errorf("Expected element to be 99, got %v", numObj.Value)
					}
				}
			},
			hasError: false,
		},
		{
			name: "vector-fill! with non-vector",
			args: []obj.Object{
				&obj.NumberObject{Value: num.Integer{Value: 5}},
				&obj.NumberObject{Value: num.Integer{Value: 99}},
			},
			hasError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create a copy of the vector for each test to avoid side effects
			testVector := &obj.VectorObject{
				Elements: make([]obj.Object, len(vector.Elements)),
			}
			copy(testVector.Elements, vector.Elements)

			args := make([]obj.Object, len(tt.args))
			copy(args, tt.args)
			// Only replace the first argument with testVector for valid test cases
			if len(args) > 0 && !tt.hasError {
				args[0] = testVector
			}

			result := VectorFill(args, env)

			if tt.hasError {
				if result.Type() != obj.ERROR_OBJ {
					t.Errorf("Expected error, got %v", result)
				}
				return
			}

			if tt.checkFn != nil {
				tt.checkFn(t, result, testVector)
			}
		})
	}
}
